package org.budo.dingtalk;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.MessageFormat;
import java.util.Formatter;
import java.util.Map;
import java.util.UUID;

import org.budo.dingtalk.error.DingdingApiRuntimeException;
import org.budo.dingtalk.error.IllegalUserIdListRuntimeException;
import org.budo.dingtalk.error.UnauthorizedUserIdRuntimeException;
import org.budo.support.http.client.HttpUtil;
import org.budo.support.http.client.Request;
import org.budo.support.http.client.Response;
import org.budo.support.lang.util.IoUtil;
import org.budo.support.lang.util.StringUtil;
import org.budo.support.slf4j.Slf4j;
import org.slf4j.Logger;

import com.alibaba.fastjson.JSON;

/**
 * 叮叮接口
 * 
 * @author li
 */
public class DingdingApi extends AbstractDingdingApi implements Serializable {
    private static final long serialVersionUID = -7050788217323471629L;

    private static final Logger log = Slf4j.getLogger();

    public static final String APPLICATION_JSON_CHARSET_UTF_8 = "application/json;charset=UTF-8";

    private String accessToken;

    public String getAccessToken() {
        return this.accessToken;
    }

    public void setAccessToken(String accessToken) {
        this.accessToken = accessToken;
    }

    public DingdingApi() {}

    public DingdingApi(String appId, String appSecret, String redirectUri) {
        this.setAppId(appId);
        this.setAppSecret(appSecret);
        this.setRedirectUri(redirectUri);
    }

    private void checkThrowError(Map<String, Object> result, String extMessage) {
        String errcode = StringUtil.toString(result.get("errcode"));
        if ("0".equals(errcode)) {
            return;
        }

        String errmsg = StringUtil.toString(result.get("errmsg"));
        if (IllegalUserIdListRuntimeException.ERR_MSG.equals(errmsg)) {
            throw new IllegalUserIdListRuntimeException("#65 result=" + result + ", extMessage=" + extMessage);
        }

        if (UnauthorizedUserIdRuntimeException.ERR_MSG.equals(errmsg)) {
            throw new UnauthorizedUserIdRuntimeException("#70 result=" + result + ", extMessage=" + extMessage);
        }

        throw new DingdingApiRuntimeException("#68 result=" + result + ", extMessage=" + extMessage);
    }

    /**
     * 创建应用
     */
    public Integer createApp(String postData) {
        String url = MessageFormat.format("https://oapi.dingtalk.com/microapp/create?access_token={0}", accessToken);
        Request request = new Request();
        request.setUrl(url);
        request.setMethod(Request.POST);
        request.setRequestBody(postData.getBytes());
        request.setContentType(APPLICATION_JSON_CHARSET_UTF_8);
        Response response = request.execute();
        log.info("createApp() accessToken={}, postData={}, response={}", accessToken, postData, response.getBody());

        String result = response.getBody();
        Map<String, Object> map = JSON.parseObject(result);

        this.checkThrowError(map, "createApp, postData=" + postData);

        return (Integer) map.get("agentid");
    }

    /**
     * 通过corpId和CorpSecret获取accessToken
     */
    public String accessToken() {
        String url = MessageFormat.format("https://oapi.dingtalk.com/gettoken?corpid={0}&corpsecret={1}", this.getAppId(), this.getAppSecret());
        String response = HttpUtil.get(url).getBody();
        log.info("accessToken() app_id={}, app_secret={}, response={}", this.getAppId(), this.getAppSecret(), response);
        Map<String, Object> map = JSON.parseObject(response);
        this.checkThrowError(map, "accessToken, appId=" + this.getAppId());

        this.accessToken = (String) map.get("access_token");
        return this.accessToken;
    }

    /**
     * 获取jsTicket
     */
    public Map<String, Object> getJsApiTicket() {
        String url = MessageFormat.format("https://oapi.dingtalk.com/get_jsapi_ticket?access_token={0}", accessToken);
        String response = HttpUtil.get(url).getBody();
        log.info("getJsApiTicket() accessToken={}, response={}", accessToken, response);
        Map<String, Object> map = JSON.parseObject(response);

        this.checkThrowError(map, "getJsApiTicket, accessToken=" + accessToken);
        return map;
    }

    /**
     * 换取用户信息
     */
    public Map<String, Object> getUserInfo(String code) {
        String url = MessageFormat.format("https://oapi.dingtalk.com/user/getuserinfo?access_token={0}&code={1}", accessToken, code);
        String response = HttpUtil.get(url).getBody();
        log.info("getUserInfo() accessToken={},code={}, response={}", accessToken, code, response);
        Map<String, Object> map = JSON.parseObject(response);

        this.checkThrowError(map, "getUserInfo, accessToken=" + accessToken + ", code=" + code);
        return map;
    }

    /**
     * 获取用户详情
     */
    public Map<String, Object> getUserInfoDetail(String userId) {
        String url = MessageFormat.format("https://oapi.dingtalk.com/user/get?access_token={0}&userid={1}", accessToken, userId);
        String response = HttpUtil.get(url).getBody();
        log.info("getUserInfoDetail() accessToken={},userid={}, response={}", accessToken, userId, response);
        Map<String, Object> map = JSON.parseObject(response);

        this.checkThrowError(map, "getUserInfoDetail, userid=" + userId + ", accessToken=" + accessToken);
        return map;
    }

    /**
     * 上传媒体文件
     */
    public String uploadMeta(String qiniu_url, String type) {
        String media_id = "";
        try {
            String url = MessageFormat.format("https://oapi.dingtalk.com/media/upload?access_token={0}&type={1}", accessToken, type);

            URL qiniu_down = new URL(qiniu_url);
            HttpURLConnection conn = (HttpURLConnection) qiniu_down.openConnection();
            conn.setDoInput(true);
            conn.connect();
            InputStream in = conn.getInputStream(); // 得到网络返回的输入流

            String response = formUpload(url, in, "media");
            Map<String, Object> map = JSON.parseObject(response);

            this.checkThrowError(map, "uploadMeta, accessToken=" + accessToken + ", type=" + type);
            media_id = (String) map.get("media_id");
        } catch (Exception e) {
            log.error("上传媒体文件失败", e);
            return null;
        }
        return media_id;
    }

    /**
     * 发送企业消息
     */
    public String sendMessage(DingMessage dingMessage) {
        String url = MessageFormat.format("https://oapi.dingtalk.com/message/send?access_token={0}", accessToken);
        log.info("钉钉发送新消息开始，URL = {},accessToken={}", url, accessToken);
        Request request = new Request();
        request.setContentType(APPLICATION_JSON_CHARSET_UTF_8);
        request.setUrl(url);
        request.setMethod(Request.POST);
        request.setRequestBody(dingMessage.getMessage().getBytes());
        Response response = request.execute();
        String result = response.getBody();
        Map<String, Object> map = JSON.parseObject(result);

        log.info("钉钉发送新消息返回结果，result = {}", result);
        this.checkThrowError(map, "sendMessage, dingMessage=" + dingMessage);

        return result;
    }

    /**
     * 签名
     */
    public static String sign(String ticket, String nonceStr, long timeStamp, String url) {
        String plain = "jsapi_ticket=" + ticket + "&noncestr=" + nonceStr + "&timestamp=" + String.valueOf(timeStamp) + "&url=" + url;
        try {
            MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
            sha1.reset();
            sha1.update(plain.getBytes("UTF-8"));
            return bytesToHex(sha1.digest());
        } catch (NoSuchAlgorithmException e) {
            throw new DingdingApiRuntimeException(e.getMessage());
        } catch (UnsupportedEncodingException e) {
            throw new DingdingApiRuntimeException(e.getMessage());
        }
    }

    private static String bytesToHex(byte[] hash) {
        Formatter formatter = new Formatter();
        for (byte b : hash) {
            formatter.format("%02x", b);
        }
        String result = formatter.toString();
        formatter.close();
        return result;
    }

    private static String formUpload(String remoteAddress, InputStream inputStream, String filedName) {
        String res = "";
        HttpURLConnection conn = null;
        String BOUNDARY = "---------------------------"; // boundary就是request头和上传文件内容的分隔符
        try {
            URL url = new URL(remoteAddress);
            conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(5000);
            conn.setReadTimeout(30000);
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Connection", "Keep-Alive");
            conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.6)");
            conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
            OutputStream out = new DataOutputStream(conn.getOutputStream());

            String filename = UUID.randomUUID().toString() + ".png";

            String contentType = "multipart/form-data; boundary=----WebKitFormBoundaryGdjw9Hv1kBBaiQOy";
            StringBuffer strBuf = new StringBuffer();
            strBuf.append("\r\n").append("--").append(BOUNDARY).append("\r\n");
            strBuf.append("Content-Disposition: form-data; name=\"" + filedName + "\"; filename=\"" + filename + "\"\r\n");
            strBuf.append("Content-Type:" + contentType + "\r\n\r\n");
            out.write(strBuf.toString().getBytes());
            DataInputStream in = new DataInputStream(inputStream);
            int bytes = 0;
            byte[] bufferOut = new byte[1024];
            while ((bytes = in.read(bufferOut)) != -1) {
                out.write(bufferOut, 0, bytes);
            }
            in.close();

            byte[] endData = ("\r\n--" + BOUNDARY + "--\r\n").getBytes();
            out.write(endData);
            out.flush();
            out.close();

            res = IoUtil.toString(conn.getInputStream());
        } catch (Throwable e) {
            log.error("#268 发送POST请求出错。" + remoteAddress + ", e=" + e, e);
        } finally {
            if (conn != null) {
                conn.disconnect();
                conn = null;
            }
        }
        return res;
    }
}